﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Threading;

namespace GW2LastOnline
{
    class Achievements
    {
        public const string REGION_NA = "na";
        public const string REGION_EU = "eu";

        private const string ACHIEVEMENTS_URL = "https://leaderboards.guildwars2.com/en/{0}/achievements";
        private const string WORLD_URL = "https://leaderboards.guildwars2.com/en/{0}/achievements/world/{1}?pajx=1&page={2}";
        private const string GUILD_URL = "https://leaderboards.guildwars2.com/en/{0}/achievements/guild/{1}?pjax=1&page={2}";

        public delegate void OnAchievementsPageUpdatedEventHandler(AchievementsPage page);
        public delegate void OnListsUpdatedEventHandler(AchievementLists lists);
        public delegate AchievementsPage GetAchievementsPageDelegate(int page);

        public event OnAchievementsPageUpdatedEventHandler OnAchievementsPageUpdated;
        public event OnListsUpdatedEventHandler OnListsUpdated;

        public struct AchievementLists
        {
            public List<string> guilds;
            public List<string> worlds;
        }

        public struct PlayerRecord
        {
            public string name;
            public int points;
            public DateTime updated;
        }

        public struct AchievementsPage
        {
            public List<PlayerRecord> records;
            public int page;
            public bool hasNext;
        }

        private Session session;
        private int currentProcess;

        public Achievements(Session session)
        {
            this.session = session;
        }

        public void RequestLists(string region)
        {
            int id;
            lock (this)
            {
                id = ++currentProcess;
            }

            new Thread(new ThreadStart(delegate
            {
                AchievementLists lists = GetLists(region);

                lock (this)
                {
                    if (id == currentProcess)
                    {
                        if (OnListsUpdated != null)
                            OnListsUpdated(lists);
                    }
                }
            })).Start();
        }

        public AchievementLists GetLists(string region)
        {
            string data = this.Get(String.Format(ACHIEVEMENTS_URL, region));

            AchievementLists lists = new AchievementLists();
            lists.guilds = this.ParseList(data, "guild");
            lists.worlds = this.ParseList(data, "world");

            return lists;
        }

        private void RequestAchievementsPage(GetAchievementsPageDelegate get)
        {
            int id;
            lock (this)
            {
                id = ++currentProcess;
            }

            new Thread(new ThreadStart(delegate
            {
                int page = 1;
                bool hasNext = true;

                while (id == currentProcess && hasNext)
                {
                    AchievementsPage p = get(page++);

                    lock (this)
                    {
                        if (id == currentProcess)
                        {
                            if (OnAchievementsPageUpdated != null)
                                OnAchievementsPageUpdated(p);
                        }
                    }

                    if (hasNext = p.hasNext)
                        Thread.Sleep(0);
                }
            })).Start();
        }

        public void RequestWorld(string region, string world)
        {
            RequestAchievementsPage(new GetAchievementsPageDelegate(
                delegate(int page)
                {
                    return GetWorld(region, world, page);
                }));
        }

        public void RequestGuild(string region, string guild)
        {
            RequestAchievementsPage(new GetAchievementsPageDelegate(
                delegate(int page)
                {
                    return GetGuild(region, guild, page);
                }));
        }

        private AchievementsPage GetAchievementsPage(string url, string id, int page)
        {
            string data = this.Get(url);
            List<PlayerRecord> records = new List<PlayerRecord>();

            AchievementsPage p = new AchievementsPage();
            p.page = page;
            p.hasNext = (data.IndexOf("page=" + (page + 1)) >= 0);
            p.records = records;

            int index = data.IndexOf("data-filter=\"" + id + "\"");
            if (index >= 0)
            {
                int last = data.IndexOf("</table>", index);
                do
                {
                    string name, lastUpdate, points;
                    index = InnerValue(data, "<td class=\"name text\">", "</td>", index, out name);
                    index = InnerValue(data, "<span class=\"cell-inner after-arrow\">", "</span>", index, out points);
                    index = InnerValue(data, "<span class=\"additional before-arrow\">", "</span>", index, out lastUpdate);

                    if (index >= 0)
                    {
                        PlayerRecord record = new PlayerRecord();
                        record.points = -1;
                        record.name = name.Trim();
                        record.updated = ParseDate(lastUpdate);
                        Int32.TryParse(points.Trim(), out record.points);

                        records.Add(record);
                    }
                }
                while (index >= 0 && index < last);
            }

            return p;
        }

        private AchievementsPage GetWorld(string region, string world, int page)
        {
            return GetAchievementsPage(String.Format(WORLD_URL, region, world, page), "world", page);
        }

        private AchievementsPage GetGuild(string region, string guild, int page)
        {
            return GetAchievementsPage(String.Format(GUILD_URL, region, guild, page), "guild", page);
        }

        private string Get(string url)
        {
            HttpWebRequest request;
            HttpWebResponse response;
            Stream stream;

            request = (HttpWebRequest)HttpWebRequest.Create(url);
            request.AllowAutoRedirect = true;
            request.Method = "GET";
            request.CookieContainer = new CookieContainer();
            request.CookieContainer.Add(session.Cookies);

            try
            {
                response = (HttpWebResponse)request.GetResponse();
                stream = response.GetResponseStream();
            }
            catch (WebException we)
            {
                if (((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.Unauthorized)
                    session.Invalidate();
                return "";
            }
            catch
            {
                return "";
            }

            StreamReader reader = new StreamReader(stream);
            string data = reader.ReadToEnd();

            response.Close();
            reader.Close();
            reader.Dispose();

            return data;
        }

        private DateTime ParseDate(string since)
        {
            int index = since.IndexOf("Since");
            if (index < 0)
                return DateTime.MinValue;

            since = since.Substring(index + 5).Trim();
            since = since.Replace("&#x2F;", "/");
            since = since.Replace("PDT", "-0700");
            since = since.Replace("PST", "-0800");

            try
            {
                return DateTime.ParseExact(since, "M/d/yy h:mmtt K", System.Globalization.CultureInfo.InvariantCulture);
            }
            catch { }

            try
            {
                since = since.Substring(0, since.LastIndexOf(' ')).Trim();
                return DateTime.ParseExact(since, "M/d/yy h:mmtt", System.Globalization.CultureInfo.InvariantCulture);
            }
            catch { }

            return DateTime.MinValue;
        }

        private int InnerValue(string data, string beggining, string ending, int startAt, out string value)
        {
            if (startAt < 0)
            {
                value = null;
                return startAt;
            }

            int index = data.IndexOf(beggining, startAt);
            if (index < 0)
            {
                value = null;
                return index;
            }

            index += beggining.Length;
            int end = data.IndexOf(ending, index);
            if (end < 0)
            {
                value = null;
                return end;
            }

            value = data.Substring(index, end - index);
            return end;
        }

        private List<string> ParseList(string data, string id)
        {
            List<string> items = new List<string>();

            int index = data.IndexOf("<select name=\"" + id + "\"");
            if (index >= 0)
            {
                int last = data.IndexOf("</select>", index);

                do
                {
                    index = data.IndexOf("<option value=", index);
                    if (index >= 0 && index < last)
                    {
                        index = data.IndexOf('>', index);
                        int end = data.IndexOf('<', index);


                        string name = data.Substring(index + 1, end - index - 1).Trim();
                        name = name.Replace("&#x27;", "'");

                        items.Add(name);
                    }
                }
                while (index >= 0 && index < last);
            }

            return items;
        }

        public void Abort()
        {
            currentProcess++;
        }
    }
}
